feat(plugin): per-plugin KV storage#1187
Open
mvanhorn wants to merge 5 commits intofloatpane:masterfrom
Open
Conversation
Adds matcha.store_set, store_get, store_delete, store_keys to the Lua plugin API per floatpane#510. Per-plugin scoping is preserved by tracking the active plugin context during plugin load, hook invocation, and keybinding callbacks. The manager attributes hooks/keybindings to their owning plugin so the storage API resolves to ~/.config/matcha/plugins/<plugin>/data.json even when multiple plugins call the same API. - plugin/storage.go: pluginStore (mutex + atomic write + 0o600 mode) - plugin/api.go: register store_set / store_get / store_delete / store_keys - plugin/plugin.go: currentPlugin tracking, KeyBinding plugin attribution - plugin/hooks.go: registeredHook captures plugin attribution; CallHook swaps currentPlugin per callback so isolation holds across plugins - plugin/storage_test.go: round-trip, persistence, concurrent writes, file mode 0o600 - plugin/api_storage_test.go: Lua-level integration + cross-plugin isolation - plugin/README.md: persistent storage section with the issue example Closes floatpane#510.
Loads a small Lua plugin twice in two separate Manager instances against the same HOME to prove the new store_set/store_get/store_delete/store_keys API persists across sessions. Captures the run as public/assets/plugin_storage_demo.gif (mode 0o600 visible at the bottom of the GIF).
- flush(): unique tmp file via os.CreateTemp + explicit Chmod(0600) before rename, preventing collision when two pluginStore instances target the same plugin and ensuring 0600 mode survives overwrite - newPluginStore: reject plugin names that aren't [a-zA-Z0-9_-]+, blocking path traversal via crafted plugin names - currentStore now returns (*pluginStore, error); Lua API distinguishes missing plugin context from real store init failures and surfaces the underlying error to the plugin author - tests: cover overwrite mode preservation, invalid name rejection, and Lua-level error propagation on store init failure
public/assets/plugin-storage-remotion-demo.gif walks through the API surface (store_set/store_get/store_delete/store_keys), session 1 writes, session 2 with a fresh Manager retrieving persisted values, and the final data.json on disk with mode 0o600. Rendered by Remotion at 1280x720, encoded via --scale=0.6 --every-nth-frame=3.
Tests use t.Setenv("HOME", t.TempDir()) for isolation, but
os.UserHomeDir() reads %USERPROFILE% on Windows, not $HOME.
Result: every test in plugin/ shared the same real Windows
profile directory and bled state into each other.
Add setTestHome() helper that sets both HOME and USERPROFILE,
and replace the inline Setenv calls. Guard the file-mode
assertions in TestPluginStoreFileMode and the Overwrite variant
behind runtime.GOOS != "windows" since NTFS does not honor
Unix permission bits the same way (0o600 reads back as 0o666).
Verified: go test ./plugin/... passes locally on macOS.
Contributor
Author
|
Pushed 138272f. The Windows failures came down to t.Setenv("HOME", t.TempDir()) not isolating anything on Windows because os.UserHomeDir() reads %USERPROFILE%, not $HOME, so every test in the package shared the real Windows profile and bled state. Added a setTestHome helper that sets both, and guarded the 0o600 mode assertions on Windows since NTFS does not honor Unix permissions the same way. go test ./plugin/... passes locally on macOS. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What?
matcha.store_set(key, value),store_get(key),store_delete(key),store_keys()to the Lua plugin API~/.config/matcha/plugins/<plugin_name>/data.jsonwith mode0o600, JSON-encoded, atomic write via unique temp file +RenameregisteredHook{fn, plugin})^[a-zA-Z0-9_-]+$is accepted as a path component, blocking traversalManagerinstances, concurrent writes, file mode0o600(including survives overwrite), invalid plugin name rejection, Lua-level error propagation on corrupt JSON, hook/keybinding plugin attributionscreenshots/cmd/plugin_storage_demo/main.goloads the same plugin in twoManagerinstances against the sameHOMEto prove cross-session persistenceSimulated demo (Remotion) — matcha theme, scripted UI, not a live capture.
Why?
This is the maintainer-spec from issue #510:
The four function names, JSON-per-plugin file format, and config-dir path all match the spec verbatim:
#510 is the gating issue for the broader plugin-API cluster (#511 hooks, #512 lifecycle, #513 account info, #514 multi-field prompts) - persistent storage is the most-requested missing piece for the existing 35-plugin marketplace.
Notes
The implementation extends the per-plugin attribution model so future plugin APIs (account info, lifecycle hooks) can reuse the same
currentPluginplumbing inManager.Closes #510.
This contribution was developed with AI assistance.